package czh.cocoa.animation;

import android.content.MutableContextWrapper;
import android.graphics.Camera;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.view.View;
import czh.cocoa.foundation.NSArray;
import czh.cocoa.foundation.NSMutableArray;
import czh.cocoa.foundation.NSMutableDictionary;
import czh.cocoa.foundation.NSNull;
import czh.cocoa.foundation.NSNumber;
import czh.cocoa.foundation.NSObject;
import czh.cocoa.foundation.NSString;
import czh.cocoa.graphics.CGColor;
import czh.cocoa.graphics.CGContext;
import czh.cocoa.graphics.CGDrawable;
import czh.cocoa.graphics.CGPicture;
import czh.cocoa.graphics.CGPoint;
import czh.cocoa.graphics.CGRect;
import czh.cocoa.graphics.CGSize;
import czh.cocoa.ui.UIApplication;
import czh.cocoa.ui.UIColor;
import czh.cocoa.ui.UIView;
import czh.cocoa.ui.UIWindow;

public class CALayer extends NSObject {
	
	public final static NSString ANIM_OPACITY = NSTR("opacity");
	public final static NSString ANIM_FRAME = NSTR("frame");
	public final static NSString ANIM_TRANSFORM = NSTR("transform");
	public final static NSString ANIM_CENTER = NSTR("center");
	public final static NSString ANIM_BOUNDS = NSTR("bounds");
	
	public final static NSString kCAGravityResize = NSTR("resize");
	public final static NSString kCAGravityCenter = NSTR("center");
	public final static NSString kCAGravityResizeAspect = NSTR("aspectx");
	
	private final static double DEFAULT_ANIM_DURATION = 0.25;

	private CALayerDelegate mDelegate;
	
	private CGDrawable mContents;
	
	private CGRect mFrame;
	
	private CGRect mBounds;
	
	private NSMutableArray mSublayers;
	
	private CALayer mSuperlayer;
	
	private CGColor mBackgroundColor;
	
	private float mOpacity;
	
	private boolean mHidden;
	
	private boolean mNeedsDisplay;
	
	private NSMutableDictionary mAnimations;
	
	private CATransform3D mTransform;
	
	private boolean mLayouting;
	
	private boolean mSuperlayouting;
	
	private NSString mContentsGravity;
	
	private boolean mMasksToBounds;
	
	private float mBorderWidth;
	
	private CGColor mBorderColor;
	
	private float mCornerRadius;
	
	private CGColor mShadowColor;
	
	private CGSize mShadowOffset;
	
	private float mShadowRadius;
	
	private float mShadowOpacity;
	
	private CGRect mInvalidateRegion;
	
	public CALayer() {
		mOpacity = 1;
		mNeedsDisplay = true;
		mFrame = CGRectMake(0, 0, 0, 0);
		mBounds = CGRectMake(0, 0, 0, 0); 
		mTransform = new CATransform3D();
		mSublayers = new NSMutableArray(3);
		mAnimations = new NSMutableDictionary();
		mContentsGravity = kCAGravityResize;
		
		mBorderColor = UIColor.blackColor().CGColor();
		
		mShadowColor = UIColor.blackColor().CGColor();
		mShadowOffset = CGSizeMake(0, -3);
		mShadowRadius = 3;
		
		mInvalidateRegion = CGRectMake(0, 0, 0, 0);
	}
	
	public void display() {
		if(mDelegate == null || !mDelegate.displayLayer(this)) {
			CGPicture contents = new CGPicture(mFrame.size);
			CGContext ctx = contents.beginContext();
			drawInContext(ctx);
			contents.endContext();
			setContents(contents);
		}
		mNeedsDisplay = false;
	}
	
	public void drawInContext(CGContext ctx) {
		if(mDelegate != null) {
			mDelegate.drawLayerInContext(this, ctx);
		}
	}
	
	public void setDelegate(CALayerDelegate delegate) {
		mDelegate = delegate;
	}
	
	public CALayerDelegate delegate() {
		return mDelegate;
	}
	
	public void setContents(CGDrawable contents) {
		mContents = contents;
		invalidateUI();
	}
	
	public CGDrawable contents() {
		return mContents;
	}
	
	public CGRect frame() {
		return new CGRect(mFrame); 
	}
	
	public CGRect bounds() {
		return new CGRect(mBounds);
	}
	
	public void setFrame(CGRect frame) {
		if(!frame.isEqual(mFrame)) {
			CGRect frame0 = mFrame;
			mFrame = new CGRect(frame);

			if (!frame0.size.isEqual(frame.size)) { 
				
				mBounds.size = new CGSize(frame.size);
				
				if(mSuperlayer != null && !mSuperlayouting) {
					mSuperlayouting = true;
					mSuperlayer.layout();
					mSuperlayouting = false;
				}
					
				if(!mSuperlayouting) { 
					layout();
				}
				
				if (mDelegate != null) {
					mDelegate.layerSizeDidChanged(this, frame0); 
				}
				
			}
			
			CAAction action = actionForKey(ANIM_FRAME, frame0);
			if(action != null) {
				action.runActionForKeyObjectArguments(ANIM_FRAME, this, null);
			} else {
				CGRect dirty = frame0.union(frame);
				invalidateUI(dirty);
			}
		}
		
	}
	
	private CGRect applyTransform(CGRect region) {
		return applyTransform(region, mTransform);
	}
	
	private CGRect applyTransform(CGRect region, CATransform3D transform) {
		Matrix m = transform.getMatrix();
		if (!m.isIdentity()) { 
			RectF rect = region.toRectF();
			Matrix matrix = new Matrix();
			matrix.preTranslate(rect.left + rect.width()/2, rect.top + rect.height()/2);
			matrix.preConcat(m);
			matrix.preTranslate(-rect.left - rect.width()/2, -rect.top - rect.height()/2);
			matrix.mapRect(rect);
			return new CGRect(rect);
		}
		return region;
	}
	
	public void setBounds(CGRect bounds) {
		if(!bounds.isEqual(mBounds)) {
			CGRect bounds0 = mBounds;
			mBounds = new CGRect(bounds);
			
			CGRect dirty;
			
			CGRect frame0 = new CGRect(mFrame);

			if (!bounds0.size.isEqual(bounds.size)) { 
				
				mFrame.size = new CGSize(bounds.size);
				
				if(mSuperlayer != null && !mSuperlayouting) {
					mSuperlayouting = true;
					mSuperlayer.layout();
					mSuperlayouting = false;
				}
					
				if(!mSuperlayouting) { 
					layout();
				}
				
				if (mDelegate != null) {
					mDelegate.layerSizeDidChanged(this, frame0); 
				}
				
				dirty = frame0.union(mFrame);
				
			} 
			else {
				
				dirty = new CGRect(mFrame);
				
			}
			
//			dirty = CGRectMake(frame0.origin.x - bounds.origin.x, 
//					           frame0.origin.y - bounds.origin.y, 
//					           bounds.size.width, bounds.size.height);
//			dirty = dirty.union(frame0);
			
			CAAction action = actionForKey(ANIM_BOUNDS, bounds0);
			if(action != null) {
				action.runActionForKeyObjectArguments(ANIM_BOUNDS, this, null);
			} else {
				invalidateUI(dirty);
			}
		}

	}
	
	public void setCenter(CGPoint point) {
		CGRect frame = new CGRect(mFrame);
		frame.origin.x = point.x - frame.size.width / 2;
		frame.origin.y = point.y - frame.size.height / 2;
		setFrame(frame);
	}
	
	public NSArray sublayers() {
		return new NSArray(mSublayers);
	}
	
	public CALayer superlayer() {
		return mSuperlayer;
	}
	
	public void addSublayer(CALayer aLayer) {
		insertSublayerAtIndex(aLayer, mSublayers.count());
	}
	
	public void insertSublayerAbove(CALayer aLayer, CALayer sublayer) {
		int index = mSublayers.indexOfObject(sublayer);
		if (index != NSNotFound) {
			insertSublayerAtIndex(aLayer, index + 1);
		} else {
			addSublayer(aLayer);
		}
	}
	
	public void insertSublayerBelow(CALayer aLayer, CALayer sublayer) {
		int index = mSublayers.indexOfObject(sublayer);
		if (index != NSNotFound) {
			insertSublayerAtIndex(aLayer, index);
		} else {
			addSublayer(aLayer);
		}
	}
	
	public void insertSublayerAtIndex(CALayer aLayer, int index) {
		if (aLayer.superlayer() != this) {
			aLayer.removeFromSuperlayer();
			aLayer.mSuperlayer = this;
			mSublayers.insertObjectAtIndex(aLayer, index);
		} else {
			mSublayers.removeObject(aLayer);
			if (index < mSublayers.count()) { 
				mSublayers.insertObjectAtIndex(aLayer, index);
			} else {
				mSublayers.addObject(aLayer);
			}
		}
		invalidateUI();
	}
	
	public void replaceSublayerWith(CALayer oldLayer, CALayer newLayer) {
		newLayer.removeFromSuperlayer();
		newLayer.mSuperlayer = this;
		
		int index = mSublayers.indexOfObject(oldLayer);
		mSublayers.replaceObjectAtIndexWithObject(index, newLayer);
		
		oldLayer.removeFromSuperlayer();
		
		invalidateUI();
	}
	
	public void removeFromSuperlayer() {
		if(mSuperlayer != null) {
			invalidateUI();
			mSuperlayer.mSublayers.removeObject(this);
			mSuperlayer = null;
		}
	}
	
	public void renderInContext(CGContext ctx) {
		
	}
	
	public CGColor backgroundColor() {
		return mBackgroundColor;
	}
	
	public void setBackgroundColor(CGColor color) {
		mBackgroundColor = color;
		invalidateUI();
	}
	
	public void setOpacity(float opacity) {
		if(mOpacity != opacity) {
			float opacity0 = mOpacity;
			mOpacity = opacity;
			
			CAAction action = actionForKey(ANIM_OPACITY, new NSNumber(opacity0));
			if(action != null) {
				action.runActionForKeyObjectArguments(ANIM_OPACITY, this, null);
			} else {
				invalidateUI();
			}
			
		}
	}
	
	public float opacity() {
		return mOpacity;
	}
	
	public void setHidden(boolean hidden) {  
		if(mHidden != hidden) {
			mHidden = hidden;
			invalidateUI();
		}
	}
	
	public boolean isHidden() {
		return mHidden;
	}
	
	public void setNeedsDisplay() {
		mNeedsDisplay = true;
		invalidateUI();
	}
	
	public void setNeedsDisplayInRect(CGRect rect) {
		mNeedsDisplay = true;
		invalidateUI(rect);
	}
	
	public boolean needsDisplay() {
		return mNeedsDisplay;
	}
	
	protected void invalidateUI(CGRect dirty) {
		CALayer superlayer = superlayer();
//		while (superlayer != null) { 
//			CGRect frame = superlayer.frame();
//			dirty.origin.x += frame.origin.x;
//			dirty.origin.y += frame.origin.y;
//			superlayer = superlayer.superlayer();
//		}
//		
//		View rootView = UIApplication.sharedApplication().getRootView();
//		rootView.invalidate(dirty.toRect()); 
		
		if (superlayer != null) { 
			CGRect frame = superlayer.frame();
			dirty.origin.x += frame.origin.x;
			dirty.origin.y += frame.origin.y;
			superlayer.invalidateUI(dirty); 
		} else {
			UIApplication app = UIApplication.sharedApplication();
			UIWindow window = app.keyWindow();
			if (window != null) {
				CALayer root = window.layer();
				if (root == this) { 
					app.getRootView().invalidate(dirty.toRect());
				}
			}
			
		}
	}
	
	private void invalidateUI() {
		CGRect dirty = frame();
		dirty = applyTransform(dirty);
		invalidateUI(dirty);   
	}
	
	public void addAnimationForKey(CAAnimation anim, NSString key) {
		mAnimations.setObjectForKey(anim, key);
		
		if(anim.duration() <= 0) {
			anim.setDuration(DEFAULT_ANIM_DURATION); 
		}
		
		if(anim.beginTime() <= 0) {
			anim.setBeginTime(System.currentTimeMillis() / 1000.0);
		}
		
		invalidateUI();
		
		CAAnimationDelegate delegate = anim.delegate();
		if (delegate != null) {
			delegate.animationDidStart(anim);
		}
		
	}
	
	public CAAnimation animationForKey(NSString key) {
		return (CAAnimation)mAnimations.objectForKey(key);
	}
	
	public void removeAnimationForKey(NSString key) {
		CAAnimation anim = animationForKey(key);
		if (anim != null) {
			CAAnimationDelegate delegate = anim.delegate();
			if (delegate != null) {
				delegate.animationDidStop(anim, anim.isFinished());
			}
			mAnimations.removeObjectForKey(key);
		}
	} 
	
	public void removeAnimation(CAAnimation anim) {
		NSArray keys = animationKeys();
		for (NSObject key : keys) {
			CAAnimation a = animationForKey((NSString) key);
			if (a == anim) {
				removeAnimationForKey((NSString) key);
				return;
			}
		}
	}
	
	public void removeAllAnimations() {
		NSArray keys = animationKeys();
		for (NSObject key : keys) {
			removeAnimationForKey((NSString)key);
		}
	}
	
	public NSArray animationKeys() {
		return mAnimations.allKeys();
	}
	
	public void setTransform(CATransform3D t) {
		CATransform3D t0 = mTransform;
		mTransform = new CATransform3D(t);
		
		CGRect region0 = applyTransform(mFrame, t0);
		CGRect region = applyTransform(mFrame, t);
		region = region.union(region0);
		
		invalidateUI(region);
	}
	
	public void setTransform(CATransformAction t) {
		
		CATransform3D transform = new CATransform3D();
		
		transform.translate(t.translate.x, t.translate.y, 0);
		
		//transform.translate(mFrame.size.width/2, mFrame.size.height/2, 0);
		
		transform.scale(t.scale.x, t.scale.y, 1);  
		transform.rotate(t.rotateZ, 0, 0, 1);
		
		float rx = t.rotateX;
		if(rx != 0) {
			Camera camera = new Camera();
			camera.rotateX(rx);
			Matrix m = new Matrix();
			camera.getMatrix(m);
			transform.getMatrix().preConcat(m);
		}
		
		float ry = t.rotateY;
		if(ry != 0) {
			Camera camera = new Camera();
			camera.rotateY(ry);
			Matrix m = new Matrix();
			camera.getMatrix(m);
			transform.getMatrix().preConcat(m);
		}
		
		//transform.translate(-mFrame.size.width/2, -mFrame.size.height/2, 0);
		
		mTransform = transform;
		
		CAAction action = actionForKey(ANIM_TRANSFORM, t);
		if(action != null) {
			action.runActionForKeyObjectArguments(ANIM_TRANSFORM, this, null);
		} else {
			invalidateUI();
		}
		
	}
	
	public CATransform3D transform() {
		return new CATransform3D(mTransform);
	}
	
	public CAAction actionForKey(NSString key, NSObject value) {
		
		if(mDelegate != null) {
			NSObject action = mDelegate.actionForLayerForKey(this, key, value);
			if(action != null) {
				if(action == NSNull.nil()) {
					return null;
				} else {
					return (CAAction)action;
				}
			}
		}
		
		if(key.isEqual(ANIM_OPACITY) || key.isEqual(ANIM_FRAME) || key.isEqual(ANIM_BOUNDS)) { 
			CABasicAnimation anim = new CABasicAnimation(key);
			anim.setFromValue(value);
			return anim;
		} else if(key.isEqual(ANIM_TRANSFORM)) {
			CABasicAnimation anim = new CABasicAnimation(key);
			anim.setToValue(value);
			return anim;
		}
		
		return null;
		
	}
	
	public void setNeedsLayout() {
		layout();
	}
	
	private void layout() {
		if (!mLayouting) {
			mLayouting = true;
			if (mDelegate != null) {
				mDelegate.layoutSublayersOfLayer(this);
			}
			mLayouting = false;
		}
	}
	
	public void setContentsGravity(NSString gravity) {
		if (mContentsGravity != gravity) {
			mContentsGravity = gravity;
			invalidateUI();
		}
	}

	public NSString contentsGravity() {
		return mContentsGravity;
	}
	
	public void setMasksToBounds(boolean mask) {
		if (mMasksToBounds != mask) {
			mMasksToBounds = mask;
			invalidateUI();
		}
	}
	
	public boolean masksToBounds() {
		return mMasksToBounds;
	}
	
	public void setBorderWidth(float width) {
		if (mBorderWidth != width) {
			mBorderWidth = width;
			invalidateUI();
		}
	}
	
	public float borderWidth() {
		return mBorderWidth;
	}
	
	public void setBorderColor(CGColor color) {
		mBorderColor = color;
		invalidateUI();
	}
	
	public CGColor borderColor() {
		return mBorderColor;
	}
	
	public void setCornerRadius(float radius) {
		mCornerRadius = radius;
		invalidateUI();
	}
	
	public float cornerRadius() {
		return mCornerRadius;
	}
	
	public void setShadowColor(CGColor color) {
		mShadowColor = color;
		invalidateUI();
	}
	
	public CGColor shadowColor() {
		return mShadowColor;
	}
	
	public void setShadowOffset(CGSize offset) {
		if (!mShadowOffset.isEqual(offset)) { 
			mShadowOffset = new CGSize(offset);
			invalidateUI();
		}
	}
	
	public CGSize shadowOffset() {
		return new CGSize(mShadowOffset);
	}
	
	public void setShadowRadius(float radius) {
		if (mShadowRadius != radius) { 
			mShadowRadius = radius;
			invalidateUI();
		}
	}
	
	public float shadowRadius() {
		return mShadowRadius;
	}
	
	public void setShadowOpacity(float opacity) {
		if (mShadowOpacity != opacity) { 
			mShadowOpacity = opacity;
			invalidateUI();
		}
	}
	
	public float shadowOpacity() {
		return mShadowOpacity;
	}
	
}


